package com.xxx.base.config;

import cn.jpush.api.JPushClient;
import com.xxx.base.spring.AsyncableEvent;
import com.xxx.base.spring.convert.CustomStringToBooleanConverter;
import com.xxx.base.util.Log;
import my.mbean.MBeanAutoConfiguration;
import my.mbean.web.MBeanServlet;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.EventListener;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import org.springframework.web.context.support.ServletRequestHandledEvent;

import javax.annotation.Resource;
import java.util.concurrent.*;

import static org.springframework.aop.interceptor.AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME;

@Configuration
public class SysAutoConfiguration {
    @Resource
    private ApplicationContext appCtx;

    @Value("${sys.mbean.prefix:/mbean}")
    private String mbeanPrefix;
    @ImportAutoConfiguration(MBeanAutoConfiguration.class)
    class mbeanConfig {
        @Bean
        public ServletRegistrationBean mBeanServletRegistrationBean() {
            ServletRegistrationBean registrationBean = new ServletRegistrationBean();
            MBeanServlet mbeanServlet = new MBeanServlet();
            registrationBean.setServlet(mbeanServlet);
            registrationBean.setName("mbeanServet");
            registrationBean.addUrlMappings(mbeanPrefix + "/*");
            return registrationBean;
        }
    }


    /**
     * 线程池中排对任务大小警告
     */
    @Value("#{${sys.executor.warn-queue-size:${sys.executor.queueCapacity:100000} / 3 * 2}}")
    private int warnQueueSize;



    /**
     * NOTE: 如果线程池在不停的创建线程, 有可能是因为 提交的 Runnable 的异常没有被处理.
     * see:  {@link ThreadPoolExecutor#runWorker(ThreadPoolExecutor.Worker)} 这里面当有异常抛出时 1128行代码 {@link ThreadPoolExecutor#processWorkerExit(ThreadPoolExecutor.Worker, boolean)}
     * 系统公用线程池
     * {@link org.springframework.scheduling.annotation.Async} 也是用的这个线程池
     * @return
     */
    static final String EXECUTOR_PREFIX = "sys.executor";
    @Bean({"executor", DEFAULT_TASK_EXECUTOR_BEAN_NAME})
    @ConfigurationProperties(prefix = EXECUTOR_PREFIX)
    @Primary
    public ThreadPoolExecutorFactoryBean executor() {
        ThreadPoolExecutorFactoryBean bean = new ThreadPoolExecutorFactoryBean() {
            @Override
            protected ThreadPoolExecutor createExecutor(
                    int corePoolSize, int maxPoolSize, int keepAliveSeconds, BlockingQueue<Runnable> queue,
                    ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler
            ) {
                return new ThreadPoolExecutor(
                        corePoolSize, maxPoolSize, keepAliveSeconds, TimeUnit.SECONDS,
                        queue, threadFactory, rejectedExecutionHandler
                ) {
                    long idleStartTime;
                    long oneMinute = TimeUnit.MINUTES.toMillis(1);
                    @Override
                    protected void beforeExecute(Thread t, Runnable r) {
                        super.beforeExecute(t, r);
                        if (idleStartTime != 0) {
                            if (System.currentTimeMillis() - idleStartTime > oneMinute) {
                                logger.info("线程池已空闲 " + ((System.currentTimeMillis() - idleStartTime) / 1000) + " 秒, 现在继续工作");
                            }
                            idleStartTime = 0;
                        }
                        if (getQueue().size() >= warnQueueSize) logger.warn("线程池正在重负运行, " + toString());
                    }

                    @Override
                    public void execute(Runnable command) {
                        try {
                            super.execute(command);
                        } catch (TaskRejectedException ex) {
                            logger.warn("线程池已不堪重负, " + toString());
                        } catch (Throwable t) {
                            logger.error("线程执行错误", t);
                        }
                    }

                    @Override
                    protected void afterExecute(Runnable r, Throwable t) {
                        super.afterExecute(r, t);
                        if (getQueue().size() == 0) idleStartTime = System.currentTimeMillis();
                    }
                };
            }
        };
        Integer corePoolSize = appCtx.getBean(Environment.class).getProperty(EXECUTOR_PREFIX + ".corePoolSize", Integer.class);
        Integer maxPoolSize = appCtx.getBean(Environment.class).getProperty(EXECUTOR_PREFIX + ".maxPoolSize", Integer.class);
        if (corePoolSize == null && maxPoolSize == null) {
            bean.setCorePoolSize(Runtime.getRuntime().availableProcessors());
            bean.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
        }
        return bean;
    }


    /**
     * 公用事件发布器
     * @param executor
     * @return
     */
    @Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public ApplicationEventMulticaster applicationEventMulticaster(Executor executor) {
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster() {
            final Log log = Log.of(getClass());
            @Override
            public void multicastEvent(final ApplicationEvent e, ResolvableType eventType) {
                ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(e));
                for (final ApplicationListener<?> listener : getApplicationListeners(e, type)) {
                    try {
                        Executor executor = getTaskExecutor();
                        if (executor != null && ((e instanceof AsyncableEvent && ((AsyncableEvent) e).isAsync()))) {
                            executor.execute(() -> invokeListener(listener, e));
                        } else invokeListener(listener, e);
                    } catch (Exception ex) {
                        log.error(ex, "事件监听出错, listener: {}, eventType: {}", listener, eventType);
                    }
                }
            }

            @SuppressWarnings("unused")
            public void clearCache() {
                removeAllListeners();
            }
        };
        multicaster.setTaskExecutor(executor);
        return multicaster;
    }


    @Value("${sys.jpush.mastersecret:}")
    private String jpushMastersecret;
    @Value("${sys.jpush.appkey:}")
    private String jpushAppkey;


    @Bean
    @ConditionalOnProperty(prefix = "sys.jpush", value = {"mastersecret", "appkey"})
    public JPushClient jPushClient() {
        return new JPushClient(jpushMastersecret, jpushAppkey);
    }


    /**
     * 跟踪请求
     */
    @Value("${sys.trace-req:false}")
    private boolean traceReq;
    /**
     * 监控请求
     */
    @Value("${sys.monitor-req:false}")
    private boolean monitorReq;
    /**
     * 监控请求 超时限制, 即: 当请求时间超出这个值时, 就会log 出当前请求
     */
    @Value("${sys.monitor-req-time-out:1000}")
    private int monitorReqTimeOut;
    /**
     * 请求超时限制, 单位毫秒
     */
    @Value("${sys.req-timeout:3000}")
    private int     reqTimeout;

    /**
     * 请求监听 {@link org.springframework.web.servlet.DispatcherServlet}
     * @param e
     */
    @EventListener(ServletRequestHandledEvent.class)
    private void postProcessRequest(ServletRequestHandledEvent e) {
        if (e.getProcessingTimeMillis() >= reqTimeout) {
            LoggerFactory.getLogger((e.getSource().getClass()))
                    .warn("超时请求: " + e.getDescription());
        } else if (monitorReq && e.getProcessingTimeMillis() >= monitorReqTimeOut) {
            LoggerFactory.getLogger((e.getSource().getClass()))
                    .info("debug req: " + e.getDescription());
        } else if (traceReq) {
            LoggerFactory.getLogger((e.getSource().getClass()))
                    .info(e.getDescription());
        }
    }


    @Bean
    @ConfigurationProperties(prefix = "sys.converter.string-to-boolean")
    public CustomStringToBooleanConverter customStringToBooleanConverter() {
        return new CustomStringToBooleanConverter();
    }


    /**
     * >= spring boot 2.0.2 quartz 手动设置执行线程池
     * {@link org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration}
     * @param executor
     * @return
     */
//    @Bean
//    public SchedulerFactoryBeanCustomizer schedulerFactoryBeanCustomizer(Executor executor) {
//        return schedulerFactoryBean -> schedulerFactoryBean.setTaskExecutor(executor);
//    }


    @EventListener(ApplicationReadyEvent.class)
    private void postApplicationReady(ApplicationReadyEvent pReadyEvent) {
        ConverterRegistry converterRegistry = pReadyEvent.getApplicationContext().getBean(ConverterRegistry.class);
        converterRegistry.addConverter(customStringToBooleanConverter());
    }


    public String getMbeanPrefix() {
        return mbeanPrefix;
    }
}
